// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#include "pch.h"
#include "BingLocationService.h"
#include "LatLon.h"
#include "Logger.h"
#include "ServiceKeys.h"
#include "WebException.h"
#include "WebServiceRegistry.h"

using namespace Weathr;
using namespace Web;

using namespace concurrency;
using namespace Platform;
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;

// Reads a Location object from the projected JSON value.
shared_ptr<Location> ReadResource(const json::value& resourceSets, size_t index)
{
    auto result = make_shared<Location>();

    const json::value& resources = resourceSets[0][L"resources"];
    const json::value& point = resources[index][L"point"];
    const json::value& coordinates = point[L"coordinates"];
    result->Latlon.Latitude = static_cast<float>(coordinates[0].as_double());
    result->Latlon.Longitude = static_cast<float>(coordinates[1].as_double());

    try
    {
        const json::value& address = resources[index][L"address"];
        result->AdminDistrict = address[L"adminDistrict"].as_string();
    }
    catch (const json::json_exception&)
    {
        // Field was not in the payload.
    }
    try
    {
        const json::value& address = resources[index][L"address"];
        result->CountryRegion = address[L"countryRegion"].as_string();
    }
    catch (const json::json_exception&)
    {
        // Field was not in the payload.
        return nullptr;
    }
    try
    {
        const json::value& address = resources[index][L"address"];
        result->Locality = address[L"locality"].as_string();
    }
    catch (const json::json_exception&)
    {
        // Field was not in the payload.
        return nullptr;
    }

    return result;
}

BingLocationService::BingLocationService()
{
    Weathr::WebServiceRegistry::RegisterWebService(this);
}

// Retrieves the location at the provided latitude and longitude coordinates.
task<shared_ptr<Location>> BingLocationService::GetLocationAsync(float latitude, float longitude)
{
    uri_builder uri(L"http://dev.virtualearth.net/REST/v1/Locations/");
    wostringstream fragment;
    fragment << latitude << L',' << longitude;
    uri.append_path(fragment.str(), true);
    uri.append_query(L"o", L"json");
    uri.append_query(L"key", BingServiceKey);

    http_client client(uri.to_uri());

    return client.request(methods::GET).then([](http_response response)
    {
        assert(IsBackgroundThread());

        unsigned long status = response.status_code();
        if (status != 200ul)
        {
            throw WebConnectionException(status);
        }

        return response.extract_json();

    }).then([latitude, longitude](task<json::value> previousTask)
    {
        assert(IsBackgroundThread());

        try
        {
            auto v = previousTask.get();

            const json::value& resourceSets = v[L"resourceSets"];
            int32_t estimatedTotal = resourceSets[0][L"estimatedTotal"].as_integer();

            if (estimatedTotal == 0)
            {
                wostringstream ss;
                ss << L"BingLocationService: request for (" << latitude << L',' << longitude << L") returned 0 resources";
                throw WebDataException(ss.str());
            }

            // Read first result.
            return ReadResource(resourceSets, 0);
        }
        catch (const json::json_exception&)
        {
            wostringstream ss;
            ss << L"BingLocationService: Failed to parse resource set for (" << latitude << L',' << longitude << ')';
            throw WebDataException(ss.str());
        }
    });
}

// Retrieves all locations that match the provided string.
task<vector<shared_ptr<Location>>> BingLocationService::GetLocationsAsync(const wstring& name)
{
    uri_builder uri(L"http://dev.virtualearth.net/REST/v1/Locations/");
    uri.append_path(name, true);
    uri.append_query(L"o", L"json");
    uri.append_query(L"key", BingServiceKey);

    http_client client(uri.to_uri());

    return client.request(methods::GET).then([](http_response response)
    {
        assert(IsBackgroundThread());

        unsigned long status = response.status_code();
        if (status != 200ul)
        {
            throw WebConnectionException(status);
        }

        return response.extract_json();

    }).then([name](task<json::value> previousTask)
    {
        assert(IsBackgroundThread());

        try
        {
            auto v = previousTask.get();

            const json::value& resourceSets = v[L"resourceSets"];
            int32_t estimatedTotal = resourceSets[0][L"estimatedTotal"].as_integer();

            if (estimatedTotal == 0)
            {
                wostringstream ss;
                ss << L"BingLocationService: request for '" << name << L"' returned 0 resources";
                throw WebDataException(ss.str());
            }

            // Read all results.
            vector<shared_ptr<Location>> results;
            for (int32_t i = 0; i < estimatedTotal; ++i)
            {
                auto res = ReadResource(resourceSets, i);
                if (res != nullptr)
                {
                    results.emplace_back(res);
                }
            }
            return results;
        }
        catch (const json::json_exception&)
        {
            wostringstream ss;
            ss << L"BingLocationService: Failed to parse resource set for '" << name << L'\'';
            throw WebDataException(ss.str());
        }
    });
}

// Retrieves the name of the service.
wstring BingLocationService::GetServiceName() const
{
    return L"Bing";
}
